import numpy as np
import random
import copy
import matplotlib.pyplot as plt


def normal_test():
    job_id = 0
    stage_id = 1
    machine_id = 2
    work_cost_matrix = [
        [[1, 2, 3, 4],
         [3, 4, 2, 2],
         [2, 3, 1, 1]],
        [[4, 2, 3, 4],
         [3, 4, 3, 3],
         [2, 3, 2, 2]],
        [[2, 2, 3, 1],
         [3, 2, 2, 2],
         [2, 4, 1, 2]]
    ]
    print(work_cost_matrix[0][0][3])


class Schedule:

    def __init__(self, machine_list, job_list, work_cost_matrix, work_sequence):
        self._machine_list = machine_list
        self._job_list = job_list
        self._work_cost_matrix = work_cost_matrix
        self._work_sequence = work_sequence
        self._machine_entity_list = []
        self._job_entity_list = []
        # 机器实体列表，用于存储机器当前的工作状态
        machineEntityList = []
        for machine_id, machine_info in enumerate(machine_list):
            machineEntity = MachineEntity(machine_info, work_cost_matrix)
            machineEntityList.append(machineEntity)

        # 工件实体列表，用于存储工件当前的工作状态
        jobEntityList = []
        for job_id, job_info in enumerate(job_list):
            jobEntity = JobEntity(job_info, work_cost_matrix)
            jobEntityList.append(jobEntity)

        self._machine_entity_list = machineEntityList
        self._job_entity_list = jobEntityList

    # 分配一个任务并更新机器和工件状态
    def eachStageDistribution(self, each_work_sequence_id, each_work_sequence_stage):
        # 获取工件的可执行开始时间
        _each_can_continue_time = 0
        for jobEntity_id, jobEntity_value in enumerate(self._job_entity_list):
            if (jobEntity_value._job_info['job_id'] == each_work_sequence_id):
                _each_can_continue_time = jobEntity_value._can_continue_time
                break

        # 获取所有机器的可执行开始时间
        min_av_time = 999999
        select_machine_id = 0
        select_start_time = -1
        for machineEntity_id, machineEntity_value in enumerate(self._machine_entity_list):
            temp_machine_id = machineEntity_value._machine_info['machine_id']
            temp_av_time = machineEntity_value._available_time
            if (temp_av_time >= _each_can_continue_time and temp_av_time <= min_av_time):
                min_av_time = temp_av_time
                select_start_time = temp_av_time
                select_machine_id = temp_machine_id
            else:
                pass

        # 如果没有找到一台机器符合要求，则默认添加至可执行时间最早的机器，且开始时间为
        # 机器可执行时间和工件可开始时间的大者
        if select_start_time == -1:
            get_earlist_machine_id = 0
            earlist_time = 9999
            for machineEntity_id, machineEntity_value in enumerate(self._machine_entity_list):
                tem_machine_id = machineEntity_value._machine_info['machine_id']
                tem_av_time = machineEntity_value._available_time
                if (tem_av_time <= earlist_time):
                    earlist_time = tem_av_time
                    get_earlist_machine_id = tem_machine_id
                else:
                    pass
            av_time = self._machine_entity_list[get_earlist_machine_id]._available_time
            select_start_time = max(av_time, _each_can_continue_time)
            select_machine_id = get_earlist_machine_id

        # 更新机器和任务状态
        self._job_entity_list[each_work_sequence_id].updateJobDoneStatus(each_work_sequence_stage,
                                                                         select_machine_id,
                                                                         select_start_time)
        # 更新机器和任务状态
        self._machine_entity_list[select_machine_id].updateMachineDoneJobStatus(each_work_sequence_id,
                                                                                each_work_sequence_stage,
                                                                                select_start_time)
        # self.drawGantt()

    # 将编码转化为可执行的工件顺序
    def code2JobStage(self, work_sequence):
        job_stage_list = []
        for job_stage_index, job_stage in enumerate(work_sequence):
            sub_work_sequence = work_sequence[0:job_stage_index]
            stage = 0
            for sub_val in sub_work_sequence:
                if (sub_val == job_stage):
                    stage = stage + 1
                else:
                    pass
            job_stage_list.append((job_stage, stage))
        return job_stage_list

    # 画甘特图
    def drawGantt(self):
        MColor = ['red', 'blue', 'yellow', 'orange', 'green', 'moccasin', 'purple', 'pink', 'navajowhite', 'Thistle',
                  'Magenta', 'SlateBlue', 'RoyalBlue', 'Aqua', 'floralwhite', 'ghostwhite', 'goldenrod',
                  'mediumslateblue',
                  'navajowhite', 'navy', 'sandybrown']
        fig = plt.figure(figsize=(15, 6), dpi=70)
        plt.rcParams['font.sans-serif'] = ['FangSong']  # 横众轴显示字体为‘仿宋’的中文标签
        plt.rcParams['axes.unicode_minus'] = False
        for each_machine in self._machine_entity_list:
            machine_id = each_machine._machine_info['machine_id']
            machine_name = each_machine._machine_info['machine_name']
            machine_job_done_list = each_machine._job_done_list
            for each_job in machine_job_done_list:
                plt.barh(machine_name, width=each_job['need_work_time'],
                         height=0.8,
                         color=MColor[each_job['job_id']],
                         edgecolor='black',
                         left=each_job['start_time'])
                temp_each_job_id = each_job['job_id']
                temp_each_stage_id = each_job['stage_id']
                temp_each_job_name = ''
                temp_each_stage_name = ''
                for i, v in enumerate(self._job_list):
                    if v['job_id'] == temp_each_job_id:
                        temp_each_job_name = v['job_name']
                        for val in v['job_stage']:
                            if val['stage_id'] == temp_each_stage_id:
                                temp_each_stage_name = val['stage_name']
                                break
                        break

                plt.text(x=each_job['start_time'] + (each_job['need_work_time'] / 2 - 0.25),
                         y=machine_id - 0.2,
                         color='black',
                         weight='bold',
                         s=temp_each_job_name + '\n' + temp_each_stage_name,
                         size=12, fontproperties='SimSun')

        plt.ylabel("机器(编号)", size=20, fontproperties='SimSun')
        plt.xlabel("时间(小时)", size=20, fontproperties='SimSun')
        plt.tick_params(labelsize=20)
        plt.tick_params(direction='in')
        plt.show()

    # 获取最大时间
    def getMaxTime(self):
        maxTime = -1
        for value in self._machine_entity_list:
            if value._available_time > maxTime:
                maxTime = value._available_time
        return maxTime


class MachineEntity:
    def __init__(self, machine_info, work_cost_matrix):
        self._machine_info = machine_info
        self._work_cost_matrix = work_cost_matrix
        self._available_time = 0
        self._job_done_list = []

    # 更新该机器的工作列表，包括工件号，工作的阶段，起始时间
    def updateMachineDoneJobStatus(self, job_id, stage_id, start_time):
        _temp_job_done = {}
        _temp_job_done['job_id'] = job_id
        _temp_job_done['stage_id'] = stage_id
        _temp_job_done['machine_id'] = self._machine_info['machine_id']
        _temp_job_done['start_time'] = start_time
        _temp_job_done['need_work_time'] = 1000
        # 加工信息表中查出该工序（某工件的某阶段）需要多少工作时间
        need_work_time = self._work_cost_matrix[job_id][self._machine_info['machine_id']][stage_id]
        _temp_job_done['need_work_time'] = need_work_time
        _temp_job_done['end_time'] = start_time + need_work_time
        # 将工作记录移入列表
        self._job_done_list.append(_temp_job_done)
        # 修改机器的可使用时间，即到该时间为止，之前该机器都不可使用
        self._available_time = _temp_job_done['end_time']


class JobEntity:
    def __init__(self, job_info, work_cost_matrix):
        self._job_info = job_info
        self._work_cost_matrix = work_cost_matrix
        self._stage_done_list = []
        self._can_continue_time = 0

    def updateJobDoneStatus(self, stage_id, machine_id, start_time):
        _temp_job_done = {}
        _temp_job_done['job_id'] = self._job_info['job_id']
        _temp_job_done['stage_id'] = stage_id
        _temp_job_done['machine_id'] = machine_id
        _temp_job_done['start_time'] = start_time
        _temp_job_done['need_work_time'] = 1000
        # 加工信息表中查出该工序（某工件的某阶段）需要多少工作时间
        need_work_time = self._work_cost_matrix[self._job_info['job_id']][machine_id][stage_id]
        _temp_job_done['need_work_time'] = need_work_time
        _temp_job_done['end_time'] = start_time + need_work_time
        # 将工作记录移入列表
        self._stage_done_list.append(_temp_job_done)
        # 修改该工件的可继续时间，即到该时间为止，之前该工件不能继续生产（因为前序步骤没完成）
        self._can_continue_time = _temp_job_done['end_time']


def test_fixed_wor_sequence_schedule_complex():
    # 机器信息
    machine_list = [
        {'machine_id': 0, 'machine_name': '1号工作台'},
        {'machine_id': 1, 'machine_name': '2号工作台'},
        {'machine_id': 2, 'machine_name': '3号工作台'},
        {'machine_id': 3, 'machine_name': '4号工作台'},
    ]
    # 工件信息
    job_list = [
        {'job_id': 0, 'job_name': '1号工件',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段'},
                       {'stage_id': 1, 'stage_name': '2阶段'},
                       {'stage_id': 2, 'stage_name': '3阶段'},
                       {'stage_id': 3, 'stage_name': '4阶段'},
                       {'stage_id': 4, 'stage_name': '5阶段'}]},
        {'job_id': 1, 'job_name': '2号工件',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段'},
                       {'stage_id': 1, 'stage_name': '2阶段'},
                       {'stage_id': 2, 'stage_name': '3阶段'},
                       {'stage_id': 3, 'stage_name': '4阶段'},
                       {'stage_id': 4, 'stage_name': '5阶段'}]},
        {'job_id': 2, 'job_name': '3号工件',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段'},
                       {'stage_id': 1, 'stage_name': '2阶段'},
                       {'stage_id': 2, 'stage_name': '3阶段'},
                       {'stage_id': 3, 'stage_name': '4阶段'},
                       {'stage_id': 4, 'stage_name': '5阶段'}]},
        {'job_id': 3, 'job_name': '4号工件',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段'},
                       {'stage_id': 1, 'stage_name': '2阶段'},
                       {'stage_id': 2, 'stage_name': '3阶段'},
                       {'stage_id': 3, 'stage_name': '4阶段'},
                       {'stage_id': 4, 'stage_name': '5阶段'}]},
        {'job_id': 4, 'job_name': '5号工件',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段'},
                       {'stage_id': 1, 'stage_name': '2阶段'},
                       {'stage_id': 2, 'stage_name': '3阶段'},
                       {'stage_id': 3, 'stage_name': '4阶段'},
                       {'stage_id': 4, 'stage_name': '5阶段'}]},
    ]
    # 加工信息，用三维数组表示,第一层数组，每一行都是一个工件，第二层开始，
    # 每一行表示该行（机器）加工第几列(第几道stage)所需时间
    # 比如0,2,0表示工件1，机器3，工序1的加工时间，为第一行中第三行的第一列的值，为2
    work_cost_matrix = [
        [[4, 2, 3, 4, 2],
         [4, 4, 2, 2, 2],
         [4, 3, 1, 1, 2],
         [4, 3, 1, 1, 1]],
        [[3, 2, 3, 4, 2],
         [3, 4, 3, 3, 5],
         [3, 3, 2, 2, 4],
         [3, 3, 2, 2, 1]],
        [[3, 2, 3, 1, 1],
         [3, 2, 2, 2, 2],
         [3, 4, 1, 2, 1],
         [3, 2, 2, 2, 1]],
        [[4, 2, 3, 1, 1],
         [3, 2, 2, 2, 2],
         [2, 4, 1, 2, 1],
         [3, 2, 2, 2, 1]],
        [[2, 2, 3, 1, 1],
         [2, 2, 2, 2, 2],
         [2, 4, 1, 2, 1],
         [2, 2, 2, 2, 1]]
    ]
    # 生产的顺序为0的第一阶段、1的1，2，3阶段，1的第二阶段，2的1，2阶段，1的第三阶段，2的最后一个阶段
    work_sequence = [0, 1, 4, 1, 0, 4, 2, 3, 3, 3, 4, 1, 2, 4, 3, 0, 2, 0, 3, 0, 2, 1, 4, 1, 2]
    # 开始安排生产计划
    each_schedule = Schedule(machine_list, job_list, work_cost_matrix, work_sequence)

    job_stage_list = each_schedule.code2JobStage(work_sequence)
    for each_job_stage in job_stage_list:
        each_job_id = each_job_stage[0]
        each_job_stage = each_job_stage[1]
        each_schedule.eachStageDistribution(each_job_id, each_job_stage)

    print(each_schedule.getMaxTime())

    each_schedule.drawGantt()

    pass


def test_fixed_work_sequence_schedule_org():
    # 机器信息
    machine_list = [
        {'machine_id': 0, 'machine_name': '1号工作台'},
        {'machine_id': 1, 'machine_name': '2号工作台'},
        {'machine_id': 2, 'machine_name': '3号工作台'}
    ]
    # 工件信息
    job_list = [
        {'job_id': 0, 'job_name': '1号工件',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段'},
                       {'stage_id': 1, 'stage_name': '2阶段'},
                       {'stage_id': 2, 'stage_name': '3阶段'},
                       {'stage_id': 3, 'stage_name': '4阶段'}]},
        {'job_id': 1, 'job_name': '2号工件',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段'},
                       {'stage_id': 1, 'stage_name': '2阶段'},
                       {'stage_id': 2, 'stage_name': '3阶段'},
                       {'stage_id': 3, 'stage_name': '4阶段'}]},
        {'job_id': 2, 'job_name': '3号工件',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段'},
                       {'stage_id': 1, 'stage_name': '2阶段'},
                       {'stage_id': 2, 'stage_name': '3阶段'},
                       {'stage_id': 3, 'stage_name': '4阶段'}]}
    ]
    # 加工信息，用三维数组表示,第一层数组，每一行都是一个工件，第二层开始，
    # 每一行表示该机器加工第几列(第几道stage)所需时间
    # 比如0,2,0表示工件1，机器3，工序1的加工时间，为第一行中最后一行的第一列的值，为2
    work_cost_matrix = [
        [[1, 2, 3, 4],
         [3, 4, 2, 2],
         [2, 3, 1, 1]],
        [[4, 2, 3, 4],
         [3, 4, 3, 3],
         [2, 3, 2, 2]],
        [[2, 2, 3, 1],
         [3, 2, 2, 2],
         [2, 4, 1, 2]]
    ]
    # 生产的顺序为0的第一阶段、1的1，2，3阶段，1的第二阶段，2的1，2阶段，1的第三阶段，2的最后一个阶段
    work_sequence = [0, 1, 1, 2, 0, 1, 2, 0, 2]
    # 开始安排生产计划
    each_schedule = Schedule(machine_list, job_list, work_cost_matrix, work_sequence)
    # 机器实体列表，用于存储机器当前的工作状态，目前已放到生产计划初始化
    # machineEntityList = []
    # for machine_id, machine_info in enumerate(machine_list):
    #     machineEntity = MachineEntity(machine_info, work_cost_matrix)
    #     machineEntityList.append(machineEntity)

    # 工件实体列表，用于存储工件当前的工作状态，目前已放到生产计划初始化
    # jobEntityList = []
    # for job_id, job_info in enumerate(job_list):
    #     jobEntity = JobEntity(job_info, work_cost_matrix)
    #     jobEntityList.append(jobEntity)
    # #目前已放到生产计划初始化
    # each_schedule._machine_entity_list = machineEntityList
    # each_schedule._job_entity_list = jobEntityList

    job_stage_list = each_schedule.code2JobStage(work_sequence)
    for each_job_stage in job_stage_list:
        each_job_id = each_job_stage[0]
        each_job_stage = each_job_stage[1]
        each_schedule.eachStageDistribution(each_job_id, each_job_stage)

    print(each_schedule.getMaxTime())

    each_schedule.drawGantt()

    # 下面开始计算，以一、两个步骤为例子,each_work_sequence_id为工件，each_work_sequence_stage为该工件的阶段
    # each_work_sequence_id = 0
    # each_work_sequence_stage = 0
    # each_schedule.eachStageDistribution(each_work_sequence_id,each_work_sequence_stage)
    #
    # each_work_sequence_id = 1
    # each_work_sequence_stage = 0
    # each_schedule.eachStageDistribution(each_work_sequence_id,each_work_sequence_stage)
    #
    # each_work_sequence_id = 1
    # each_work_sequence_stage = 1
    # each_schedule.eachStageDistribution(each_work_sequence_id,each_work_sequence_stage)
    #
    # each_work_sequence_id = 2
    # each_work_sequence_stage = 0
    # each_schedule.eachStageDistribution(each_work_sequence_id,each_work_sequence_stage)
    #
    # each_work_sequence_id = 0
    # each_work_sequence_stage = 1
    # each_schedule.eachStageDistribution(each_work_sequence_id,each_work_sequence_stage)
    #
    # each_schedule.drawGantt()
    pass


def test_fixed_work_sequence_schedule_simple():
    # 机器信息
    machine_list = [
        {'machine_id': 0, 'machine_name': '1号工作台'},
        {'machine_id': 1, 'machine_name': '2号工作台'}
    ]
    # 工件信息
    job_list = [
        {'job_id': 0, 'job_name': '1号工件',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段'},
                       {'stage_id': 1, 'stage_name': '2阶段'},
                       {'stage_id': 2, 'stage_name': '3阶段'},
                       {'stage_id': 3, 'stage_name': '4阶段'}]},
        {'job_id': 1, 'job_name': '2号工件',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段'},
                       {'stage_id': 1, 'stage_name': '2阶段'},
                       {'stage_id': 2, 'stage_name': '3阶段'},
                       {'stage_id': 3, 'stage_name': '4阶段'}]},
        {'job_id': 2, 'job_name': '3号工件',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段'},
                       {'stage_id': 1, 'stage_name': '2阶段'},
                       {'stage_id': 2, 'stage_name': '3阶段'},
                       {'stage_id': 3, 'stage_name': '4阶段'}]}
    ]
    # 加工信息，用三维数组表示,第一层数组，每一行都是一个工件，第二层开始，
    # 每一行表示该机器加工第几列(第几道stage)所需时间
    # 比如0,2,0表示工件1，机器3，工序1的加工时间，为第一行中最后一行的第一列的值，为2
    work_cost_matrix = [
        [[1, 2, 3, 4],
         [3, 4, 2, 2]],
        [[4, 2, 3, 4],
         [3, 4, 3, 3]],
        [[2, 2, 3, 1],
         [4, 2, 2, 2]]
    ]
    # 生产的顺序为0的第一阶段、1的1，2，3阶段，1的第二阶段，2的1，2阶段，1的第三阶段，2的最后一个阶段
    work_sequence = [0, 1, 1, 2, 0, 1, 2, 0, 2]
    # 开始安排生产计划
    each_schedule = Schedule(machine_list, job_list, work_cost_matrix, work_sequence)

    job_stage_list = each_schedule.code2JobStage(work_sequence)
    for each_job_stage in job_stage_list:
        each_job_id = each_job_stage[0]
        each_job_stage = each_job_stage[1]
        each_schedule.eachStageDistribution(each_job_id, each_job_stage)

    print(each_schedule.getMaxTime())

    each_schedule.drawGantt()

    pass


if __name__ == '__main__':
    # test_fixed_work_sequence_schedule_org()
    # test_fixed_work_sequence_schedule_simple()
    test_fixed_wor_sequence_schedule_complex()

    pass
